package smb;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.net.*;
import java.io.*;

public class Smb {
	private int _listenPort;
	private String _statName;
	private int _statPort;
	private ServerSocket _serverSocket = null;
	private DatagramSocket _udpSocket = null;
	private Socket _clientSocket = null;
	private String _listenIp;
	private BufferedReader _welcomeIn;
	private boolean _moreMessages = false;
	private String _defaultMessage =  "Hello!!!";

	public Smb(int listenPort, String statName, int statPort){
		_listenPort = listenPort;
		_statName = statName;
		_statPort = statPort;	

		try {
			_welcomeIn = new BufferedReader(new FileReader("welcomeMessages.txt"));
			_moreMessages = true;
		} catch (FileNotFoundException e) {
			System.err.println("Could not open quote file. Serving default message.");
		}
	}

	private void receiveOk() throws IOException{
		byte[] buf = new byte[256];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);		

		_udpSocket.receive(packet);

		/*
		 * Shouls we test if the ip and port are the same of the stat server
		 */
		Scanner scanner = new Scanner(new String(packet.getData(), 0, packet.getLength()));
		String received = scanner.nextLine();

		if( !received.equals("Ok")){
			System.err.println("Protocol error.");
			System.err.println("Expected message:Ok");
			System.err.println("Received:" + received);
			System.exit(1);			
		}  	

	}

	private void sendAck(String request) throws IOException{
		int MAX_RETRY = 3;
		int tryNum = 0;

		while(true){
			try {
				send(request);
				receiveOk();
				break;
			} catch (SocketTimeoutException e) {
				if( tryNum == MAX_RETRY)
					throw e;
				else
					tryNum++;
			}			
		}	
	}

	private void send(String request) throws IOException{
		byte[] buf = request.getBytes();
		InetAddress statAddress = InetAddress.getByName(_statName);		
		DatagramPacket packet = new DatagramPacket(buf, buf.length, statAddress, _statPort);			
		try {
			_udpSocket.send(packet);	
		} catch (IOException e) {
			System.err.println("Could not send message to" + statAddress + ":" + _statPort);
			throw e;
		}
	}

	private void register() throws IOException{		
		// regist in stat server       
		String request = "REG " + _listenIp + " " + _listenPort + '\n';
		sendAck(request);	
	}

	private void update(String userName) throws IOException{		
		String request = "UPD " + _listenIp + " " + _listenPort + " " + userName + '\n';		
		sendAck(request);		
	}

	public void run() throws IOException{
		try{
			_listenIp = InetAddress.getLocalHost().getHostAddress();

			try {
				_serverSocket = new ServerSocket(_listenPort);
			} catch (IOException e) {
				System.err.println("Could not listen TCP on port:" + _listenPort);
				throw e;
			}

			try {
				_udpSocket = new DatagramSocket(_listenPort);     
			} catch (SocketException e) {
				System.err.println("Could not listen UDP on port:" + _listenPort);
				throw e;
			}			

			try {
				register();
			} catch (IOException e) {
				System.err.printf("Failed to register at STAT server %s:%s\n", _statName, _statPort);
				System.err.println("Continuing...");
			}			

			while(true){

				try {
					_clientSocket = _serverSocket.accept();
				} catch (IOException e) {
					System.err.println("Accept failed.");
					throw e;
				}

				PrintWriter out = new PrintWriter( _clientSocket.getOutputStream() , true);
				BufferedReader in = new BufferedReader(new InputStreamReader( _clientSocket.getInputStream()));

				String inputLine, outputLine; 

				inputLine = in.readLine();

				String[] parts = inputLine.split(" ");        

				if( parts.length != 2 && !parts[0].equals("REQ") ){
					System.err.println("Protocol error.");
					System.err.println("Expected message with format: \"REQ username\" ");
					System.err.println("Received:" + inputLine);
					throw new IOException("Protocol error");
				}

				String userName = parts[1];

				outputLine = "Ok " + getRandomMessage();

				out.println(outputLine);

				out.close();
				in.close();
				_clientSocket.close();

				update(userName);				   		
			}

		} finally{
			if(_serverSocket != null)_serverSocket.close();
			if(_udpSocket != null)_udpSocket.close();
			if(_clientSocket != null)_clientSocket.close();
		}



		//serverSocket.close();
	}

	private String getRandomMessage(){
		String message = _defaultMessage;

		if( _moreMessages){
			try {
				if( (message = _welcomeIn.readLine()) == null) {
					message = _defaultMessage;
					_moreMessages = false;
					_welcomeIn.close();					
				}
			} catch (IOException e) {
				_moreMessages = false;
			}
		}	

		return message;
	}

	public static void main(String[] args) throws IOException{
		int smbPort = 58043;
		String statName = "localhost";
		int statPort = 59000;

		Map<String,String> argsPairs = new HashMap<String,String>();

		if(args.length % 2 != 0){
			System.err.println("Invalid arguments");
			System.exit(1);
		}

		for(int i=0; i < args.length; i += 2){
			argsPairs.put(args[i], args[i+1]);			
		}		

		if( argsPairs.get("-p") != null){
			try {
				smbPort = Integer.parseInt( argsPairs.get("-p"));
			} catch (NumberFormatException e) {
				System.err.println("Command line \"-p\" argument must be an integer");
				System.exit(-1);
			}	
		}					

		if( argsPairs.get("-n") != null)
			statName = argsPairs.get("-n");

		if( argsPairs.get("-t") != null){
			try {
				statPort = Integer.parseInt( argsPairs.get("-t") );
			} catch (NumberFormatException e) {
				System.err.println("Command line \"-t\" argument must be an integer");
				System.exit(-1);
			}	
		}


		Smb server = new Smb(smbPort, statName, statPort);

		server.run();

	}
}
